-module(ggt).

-export([start/5]).

-import(werkzeug,[logging/2,timeMilliSecond/0,reset_timer/3]).

%Einstiegspunkt ins Programm, wird von starter aufgerufen		Regestriert sich auf eigener node und Bindet sich beiom Namensdienst, dancah Hello an Koordinator
start(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit) -> 	register(GgtName,self()),
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," auf eigener Node: ",node()," regestriert."])),
																Nameservice ! {self(),{rebind,GgtName,node()}},
																receive		ok -> writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," beim Namensdienst gebunden"])) end,
																Koordinator ! {hello,GgtName},
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," beim Kooridnator mit hello angemeldet"])),
																runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,{0,0},0,time(),0,false).

%hauptschleife, reagiert auf alle nötigen Messages, LastContact nötig für Term/2 abgleich bei einer weiterleitung der Abstimmung, Terminateflag nötig um festzustellen ob man zwischendurch immer noch keine nachrichten bekommen hat																
runGgt(GgtName,Koordinator,Nameservice ,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,Terminate) ->
																%Nachbarn werden einmalig beim Namensdient abgefragt und gebunden 
																%Neighbours ist ein Tuple mit {LinkerNachbar,RechterNachbar} wobei LinkerNachbar und Rechternachbar ebenfalls ein Tuple sind
																%Aufgelöst sieht Neighbours so aus: {{LinkerName,LinkerNode},{RechterName,RechterNode}}
																%Setneighbours und set PM MÜSSEN vor allem anderen erfolgen, sonst inkonsistenter zustand, da wir aber nicht mir mehreren Zuständen arbeiten sollen, ist alles in einem receive block.
						receive	{setneighbors,LeftN,RightN} -> 	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," vom Koordinator linken Nachbarn: ", LeftN," und rechten Nachbarn: ", RightN," bekommen."])),
																Nameservice ! {self(),{lookup,LeftN}},
																receive	{pin,{NameL,NodeL}}	->	LeftNeighbour = {NameL,NodeL} end,
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," vom Namensdienst linken Nachbarn: ", NameL," auf dem Node: ",NodeL,"erfahren und gebunden."])),
																Nameservice ! {self(),{lookup,RightN}},
																receive	{pin,{NameR,NodeR}}	->	RightNeighbour = {NameR,NodeR} end,
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," vom Namensdienst rechten Nachbarn: ", NameR," auf dem Node: ",NodeR,"erfahren und gebunden."])),
																NewNeighbours = {LeftNeighbour,RightNeighbour},
																runGgt(GgtName, Koordinator,Nameservice,Arbeitszeit,Termzeit,NewNeighbours,Mi,time(),TermTimer,false);
																%PM wird von koordinator gesetzt und der Abstimmungstimer wird gestartet
								{setpm,MiNeu}				-> 	CurrentMi = MiNeu,
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," setpm mit: ", werkzeug:to_String(CurrentMi)," vom Koordinator gesetzt bekommen."])),
																timer:cancel(TermTimer),
																{ok,NewTermTimer} = timer:send_after(Termzeit*1000, self(), 'starteAbstimmung'),
																runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,CurrentMi,time(),NewTermTimer,false);
																%anstoß für eine Berechnung TermTimer wird hier zurückgesetzt.
								{sendy,Y}					-> 	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," neues Mi: ", werkzeug:to_String(Y)," bekommen"])),
																NewTermTimer = werkzeug:reset_timer(TermTimer,Termzeit,'starteAbstimmung'),
																if
																%Wenn eigenes MI > als empfangenes Mi dann berechner einen Schritt und schlafe für Arbeitszeit Sekunden.
																Mi>Y	->	CurrentMi = calculateGgt(Mi,Y),
																			writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," neues Mi:", werkzeug:to_String(CurrentMi)," berechnet, simuliert ab jetzt Arbeitszeit."])),
																			timer:sleep(Arbeitszeit*1000),
																			writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," Fertig mit simulieren."])),
																			%sobald Sleep vorbei ist, informiere beide Nachbarn über das neu errechnete Mi.
																			{LeftNeighbour,RightNeighbour} = Neighbours,
																			LeftNeighbour ! {sendy,CurrentMi},
																			writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," neues Mi:", werkzeug:to_String(CurrentMi)," an linken Nachbarn gesendet."])),
																			RightNeighbour ! {sendy,CurrentMi},
																			writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," neues Mi:", werkzeug:to_String(CurrentMi)," an rechten Nachbarn gesendet."])),
																			%Zum Schluss auch noch den Koordinator über neues Mi informieren
																			Koordinator ! {briefmi,{GgtName,CurrentMi,timeMilliSecond()}},
																			writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," ueber neues Mi:", werkzeug:to_String(CurrentMi)," Koordinator informiert."])),
																			runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,CurrentMi,time(),NewTermTimer,false);
																%wenn Y>Mi ignoriere nachricht.
																true	->	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," empfangenes MI ist groeßer als eigenes MI."])),
																			runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,time(),NewTermTimer,Terminate)
																end;
																%empfängt ping, sendet pong als antwort
								{pingGGT,From}				->	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," ping vom Koordinator erhalten, sende Pong zurueck."])),
																From ! {pongGGT,GgtName},
																runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,Terminate);
																%Abstimmungsnachricht empfangen.
								{abstimmung,Initiator}		->	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," Abstimmungsnachricht von ", Initiator," erhalten"])),
																%Berechnet die Zeitdifferenz in sekunden
																TimeDifference = calculateTimeDifferenceInSeconds(time(),LastContact),
																if
																%Wenn die Abstimmung von dem GgtProzess selber ist, informiert er den Koordinator über Terminierung und wartet auf weitere befehle.
																Initiator == GgtName andalso Terminate == true	-> 	Koordinator ! {briefterm,{GgtName,Mi,werkzeug:timeMilliSecond()},self()},
																													writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName,"eigene Abstimmungsnachricht bekommen, Koordiantor mit briefterm informiert."])),
																													runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,Terminate);
																%Wenn abstimmung nicht vom Ggt Prozess selber ist, aber die Zeit zwischen den letzten SendY > ist als Termzeit/2 dann abstimmung weiterleiten an rechten nachbarn
																Initiator =/= GgtName andalso TimeDifference>(Termzeit/2) ->			{_,RightNeighbour}=Neighbours,
																																		writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," Abstimmungsnachricht an rechten Nachbarn weitergeleitet."])),
																																		RightNeighbour ! {abstimmung,Initiator},
																																		runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,Terminate);
																%Wenn der Ggtprozess kurz zuvor gerechnet hat dann wird die abstimmung verworfen.
																true ->																	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," Abstimmungsnachricht verworfen."])),
																																		runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,Terminate)
																end;
																%Schickt auf Anfrage das Aktuelle Mi an den Absender
								{tellmi,From}				->	From ! {mi,Mi},
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," tellmi vom Koordinator bekommen, mit: Mi=", werkzeug:to_String(Mi)," geantwortet."])),
																runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,Terminate);
																%Startet die Abstimmung über die Termionierung
								starteAbstimmung			->	{_,RightNeighbour}=Neighbours,
																RightNeighbour ! {abstimmung,GgtName},
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," Abstimmung gestartet"])),
																runGgt(GgtName,Koordinator,Nameservice,Arbeitszeit,Termzeit,Neighbours,Mi,LastContact,TermTimer,true);
																%beendet den GgtProzess.
								kill						-> 	writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," kill befehl bekommen, beende GGT-Prozess."])),
																Nameservice ! {self(),{unbind, GgtName}},
																receive ok -> writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," beim Namensdienst ausgetragen"])) end,
																unregister(GgtName),
																writeToLog(lists:concat(["Ggt_",GgtName]),lists:concat([GgtName," auf Node unregister aufgerufen."])),
																bye
						end.
							
							
%berechnet einen Schritt der Gesamten Ggt-Berechnung							
calculateGgt(Mi,Y) -> 	((Mi-1)rem Y)+1.

%Logging funktion
writeToLog(MyLogFileName, Text) ->  werkzeug:logging(lists:concat(["logfiles/",MyLogFileName,".log"]), lists:concat([Text,"\r\n"])).

%Berechnet den Zeitunterschied in sekunden, Eingabeparameter: Now&Past:Triple mit Stunden,Minuten,Sekunden
calculateTimeDifferenceInSeconds(Now,Past) ->	{HoursNow,MinutesNow,SecondsNow}=Now,
												{HoursPast,MinutesPast,SecondsPast} = Past,
												((HoursNow-HoursPast)*3600)+((MinutesNow-MinutesPast)*60)+(SecondsNow-SecondsPast).
												
												
												
												
												